//Precompiled Header [ALWAYS ON TOP IN CPP]
#include "stdafx.h"

#include "CameraComponent.h"
#include "../Base/OverlordGame.h"
#include "../Components/TransformComponent.h"
#include "../Diagnostics/Logger.h"
#include "../Physx/PhysxManager.h"
#include "../Physx/PhysxProxy.h"
#include "../Scenegraph/GameObject.h"
#include "../Scenegraph/GameScene.h"
#include "../Diagnostics/DebugRenderer.h"

CameraComponent::CameraComponent(void):
	m_FOV(XM_PIDIV4),
	m_NearPlane(0.1f),
	m_FarPlane(2500.0f),
	m_Size(25.0f),
	m_PerspectiveProjection(true),
	m_IsActive(true)
{
	XMStoreFloat4x4(&m_Projection, XMMatrixIdentity());
	XMStoreFloat4x4(&m_View, XMMatrixIdentity());
	XMStoreFloat4x4(&m_ViewInverse, XMMatrixIdentity());
	XMStoreFloat4x4(&m_ViewProjection, XMMatrixIdentity());
	XMStoreFloat4x4(&m_ViewProjectionInverse, XMMatrixIdentity());
}


CameraComponent::~CameraComponent(void)
{
}

void CameraComponent::Initialize(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);
}

void CameraComponent::Update(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);
	auto windowSettings = OverlordGame::GetGameSettings().Window;
	XMMATRIX projection, view, viewInv, viewProjectionInv;

	if(m_PerspectiveProjection)
	{
		projection = XMMatrixPerspectiveFovLH(m_FOV, windowSettings.AspectRatio ,m_NearPlane, m_FarPlane);
	}
	else
	{
		float viewWidth = (m_Size>0) ? m_Size * windowSettings.AspectRatio : windowSettings.Width;
		float viewHeight = (m_Size>0) ? m_Size : windowSettings.Height;
		projection = XMMatrixOrthographicLH(viewWidth, viewHeight, m_NearPlane, m_FarPlane);
	}

	XMVECTOR worldPosition = XMLoadFloat3(&GetTransform()->GetWorldPosition());
	XMVECTOR lookAt = XMLoadFloat3(&GetTransform()->GetForward());
	XMVECTOR upVec = XMLoadFloat3(&GetTransform()->GetUp());

	view = XMMatrixLookAtLH(worldPosition, worldPosition + lookAt, upVec);
	viewInv = XMMatrixInverse(nullptr, view);
	viewProjectionInv = XMMatrixInverse(nullptr, view * projection);

	XMStoreFloat4x4(&m_Projection, projection);
	XMStoreFloat4x4(&m_View, view);
	XMStoreFloat4x4(&m_ViewInverse, viewInv);
	XMStoreFloat4x4(&m_ViewProjection, view * projection);
	XMStoreFloat4x4(&m_ViewProjectionInverse, viewProjectionInv);
}

void CameraComponent::Draw(const GameContext& gameContext)
{
	UNREFERENCED_PARAMETER(gameContext);
}

void CameraComponent::SetActive()
{
	auto gameObject = GetGameObject();
	if(gameObject == nullptr)
	{
		Logger::LogError(L"[CameraComponent] Failed to set active camera. Parent game object is null");
		return;
	}

	auto gameScene = gameObject->GetScene();
	if(gameScene == nullptr)
	{
		Logger::LogError(L"[CameraComponent] Failed to set active camera. Parent game scene is null");
		return;
	}

	gameScene->SetActiveCamera(this);
}

GameObject* CameraComponent::Pick(const GameContext& gameContext) const
{
	// Step 1: get the GameObject, GameScene and PhysXProxy
	//		   be sure to include safety checks!!
	auto Obj = GetGameObject();
	auto scene = Obj->GetScene();
	auto pxProxy = scene->GetPhysxProxy();

	// Step 2: created variable declaration for
	//			* near point and far point (float3)
	//			* NDC (float2)
	//			* ray start and ray direction (PxVec3)
	//			* halfwidth and halfheight (Hint: OverlordGame::GetGameSettings holds usefull data)
	//			* mouse position (the game context holds usefull data)
	
	XMFLOAT2 ndc;
	//PxVec3 rayStart, rayDirecion;
	float halfWidth = (float)OverlordGame::GetGameSettings().Window.Width/2;
	float halfHeight = (float)OverlordGame::GetGameSettings().Window.Height/2;
	auto mousepos = gameContext.pInput->GetMousePosition();

	// Step 3: Calculate x and y ndc coordinates
	ndc.x = (mousepos.x - halfWidth)/halfWidth;
	ndc.y = -(mousepos.y - halfHeight)/halfHeight;

	// Step 4: Calculate near point and far point using the NDC coordinates and the inverse of the viewProjection matrix
	XMFLOAT3 farPoint(ndc.x, ndc.y, 1);
	XMFLOAT3 nearPoint(ndc.x, ndc.y, 0);
	XMMATRIX invViewProj = XMLoadFloat4x4(&m_ViewProjectionInverse);
	XMVECTOR vecNearPoint = XMLoadFloat3(&nearPoint);
	XMVECTOR vecFarPoint = XMLoadFloat3(&farPoint);
	XMVECTOR returnNearPoint = XMVector3TransformCoord(vecNearPoint, invViewProj);
	XMVECTOR returnFarPoint = XMVector3TransformCoord(vecFarPoint, invViewProj);
	XMStoreFloat3(&nearPoint, returnNearPoint);
	XMStoreFloat3(&farPoint, returnFarPoint);

	// Step 5: Calculate the ray start and the direction (hint: the direction can be calculated using near point and far point)
	PxVec3 origin(nearPoint.x, nearPoint.y, nearPoint.z);
	PxVec3 direction(farPoint.x - nearPoint.x, farPoint.y - nearPoint.y, farPoint.z - nearPoint.z);
	direction.normalize();
	
	// Step 6: Use the PhysxProxy to do a raycast. 
	//		   If the raycast has a hit, return the GameObject associated with the actor
	//		   (Hint: the actor can be fount in hit.block. The userData field of the actor should be a Component)
	PxRaycastBuffer hit;
	if(pxProxy->Raycast(origin, direction, PX_MAX_F32, hit) )
	{
		if (hit.block.actor->userData != nullptr)
		{
			BaseComponent* object = (BaseComponent*) hit.block.actor->userData;
			return object->GetGameObject();
		}
	}

	DebugRenderer::DrawLine(nearPoint, XMFLOAT4(1,0,0,0), farPoint, XMFLOAT4(1,0,0,0));
	
	return nullptr;
}